/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package fr.loof.fonzie.parsers;

import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;

import fr.loof.fonzie.PersistenceQuery;
import fr.loof.fonzie.keywords.Aggregate;

public class JPQLPersistenceQuery
    implements PersistenceQuery
{
    private static Logger logger = LoggerFactory.getLogger( JPQLPersistenceQuery.class );

    private Class rootEntity;

    private String rootEntityShortName;

    private Class currentEntity;

    private String currentProperty;

    private String currentEntityShortName;

    private StringBuilder select = new StringBuilder();

    private StringBuilder jpql = new StringBuilder();

    private List<String> parameters = new LinkedList<String>();

    private List<Class> types = new LinkedList<Class>();

    /**
     * {@inheritDoc}
     *
     * @see fr.loof.fonzie.PersistenceQuery#parameters()
     */
    public Iterator<String> parameters()
    {
        return parameters.iterator();
    }

    public JPQLPersistenceQuery( Class entity, Aggregate aggregate )
    {
        this( entity );
        select.append( "select " );
        logger.trace( "applying aggregate {}", aggregate );
        select.append( aggregate.apply( currentEntityShortName ) );
        select.append( " from " ).append( getEntityName() ).append( " " ).append( currentEntityShortName );
    }

    public JPQLPersistenceQuery( Class entity, String jpql )
    {
        this( entity );
        append( jpql );
    }

    private JPQLPersistenceQuery( Class entity )
    {
        super();
        this.rootEntity = entity;
        this.currentEntity = entity;
        this.shortNames = new HashSet<String>();
        computeCurrentEntityShortName();
        this.rootEntityShortName = currentEntityShortName;
    }

    private void computeCurrentEntityShortName()
    {
        String sn;
        int i = 1;
        do
        {
            sn = getEntityName().substring( 0, i++ ).toLowerCase();
        }
        while ( shortNames.contains( sn ) );
        currentEntityShortName = sn;
        shortNames.add( sn );
    }

    private String getEntityName()
    {
        Entity annotation = AnnotationUtils.findAnnotation( currentEntity, Entity.class );
        if ( annotation != null )
        {
            String name = annotation.name();
            if ( name != null && name.length() > 0 )
            {
                return name;
            }
        }
        return currentEntity.getSimpleName();
    }

    public void add( String name, Class type )
    {
        parameters.add( name );
        types.add( null );
    }

    public JPQLPersistenceQuery append( String str )
    {
        if ( jpql.length() == 0 )
        {
            jpql.append( " where" );
        }
        jpql.append( str );
        return this;
    }

    public JPQLPersistenceQuery appendSelect( String str )
    {
        select.append( str );
        return this;
    }

    public String toString()
    {
        return select.toString() + jpql.toString();
    }

    /**
     * {@inheritDoc}
     *
     * @see fr.loof.fonzie.PersistenceQuery#getEntity()
     */
    public Class getEntity()
    {
        return rootEntity;
    }

    private Set<String> shortNames;

    public void setCurrentEntity( Class entity )
    {
        this.currentEntity = entity;
        computeCurrentEntityShortName();
    }

    public String getCurrentEntityShortName()
    {
        return currentEntityShortName;
    }

    /**
     * {@inheritDoc}
     *
     * @see fr.loof.fonzie.PersistenceQuery#createQuery(javax.persistence.EntityManager)
     */
    public Query createQuery( EntityManager entityManager )
    {
        StringBuffer q = new StringBuffer( select ).append( jpql );
        return entityManager.createQuery( q.toString() );
    }

    public Class getCurrentEntity()
    {
        return currentEntity;
    }

    public String getRootEntityShortName()
    {
        return rootEntityShortName;
    }

    public void setCurrentProperty( String currentProperty )
    {
        this.currentProperty = currentProperty;
    }

    public String getCurrentProperty()
    {
        return currentProperty;
    }
}